/*
 * FeatureVector.java
 *
 * Created on July 27, 2006, 12:39 PM
 *
 */

package galronnlp.perceptron;

import java.util.Enumeration;
import java.util.Hashtable;

/**
 * This is an implementation of a sparse feature vector, wherein features with
 * value 0 can be left out of the vector. The feature vector maps features to
 * doubles. Feature Vectors are used in three ways in the implementation of the
 * perceptron parser. The first is as a local feature vector. Local feature vectors
 * exist on each node in a (partial) parse tree. Each value in a local feature
 * vector is either 0 or 1 (or does not exist). Local feature vectors are essentially
 * binary feature vectors, indicating whether or not a certain feature exists on
 * that node. Global feature vectors are feature vectors on an entire tree. Global
 * feature vectors are the sum of the local feature vectors of each node in the tree.
 * Finally, the <code>FeatureVector</code> object is used to represent the parameter
 * vector for parsing.
 *
 * @author Daniel A. Galron
 */
public class FeatureVector {
    
    private Hashtable<Feature, Double> features = new Hashtable<Feature, Double>();
    
    /** Creates a new instance of FeatureVector */
    public FeatureVector() {
    }
    
    public int size() {
        return features.size();
    }
    
    /** Creates a new instance of FeatureVector from an already existing Hashtable */
    public FeatureVector(Hashtable<Feature, Double> f) {
        this.features = f;
    }

    public void addFeature(Feature f, double i) {
        features.put(f, new Double(i));
    }
    
    /**
     * Firing a feature means adding the feature to the hashtable with count 1.
     * Firing features on the node level means assigning a boolean value (0 or 1)
     * to indicate whether or not the feature is present at a node in the tree.
     */
    public void fire(Feature f) {
        if(f.node() != null)
            features.put(f, new Double(1));
    }
    
    public String getXMLRep() {
        String ret = "<parameters>\n";
        for(Enumeration<Feature> en = features.keys(); en.hasMoreElements();) {
            Feature f = en.nextElement();
            ret += "\t" + f.getXMLRep() + "value=\"" + features.get(f) + "\"/>\n";
        }
        ret += "</parameters>\n";
        return ret;
    }
    
    /**
     * Returns the dot product of two vectors. Note that both this vector and
     * the other vector are expanded so that they have the same size. The return
     * vector is then compressed to remove features of size 0.
     */
    public FeatureVector dotProduct(FeatureVector other) {
        // Expand this feature vector with the entries in the other and copy it
        // to ret
        FeatureVector ret = this.expand(other);
        // Expand the other feature vector with the entries in this one
        FeatureVector o = other.expand(ret);
        // This should never happen.
        if(ret.features.size() != o.features.size()) {
            System.err.println("ERROR!");
            System.exit(1);
        }
        // Go through all the features, and put the product of each feature in
        // the return vector
        for(Enumeration<Feature> en = ret.features.keys(); en.hasMoreElements();) {
            Feature f = en.nextElement();
            ret.features.put(f, ret.features.get(f) * o.features.get(f));
        }
        // Remove all 0 entries from the vector and return it.
        return ret.compress();
    }
    
    // For each entry which is in 'other' which is not in 'this', create an entry
    // in the vector and set it equal to 0
    public FeatureVector expand(FeatureVector other) {
        FeatureVector fv = this.copy();
        for(Enumeration<Feature> en = other.features.keys(); en.hasMoreElements();) {
            Feature f = en.nextElement();
            if(!fv.features.containsKey(f)) {
                fv.features.put(f, new Double(0));
            }
        }
        return fv;
    }
    
    // Removes all features with count 0 from the hashtable
    public FeatureVector compress() {
        FeatureVector fv = this.copy();
        for(Enumeration<Feature> en = fv.features.keys(); en.hasMoreElements();) {
            Feature f = en.nextElement();
            if(fv.features.get(f).doubleValue() == 0)
                fv.features.remove(f);
        }
        return fv;
    }
    
    /**
     * Returns the element-wise sum between this feature vector and some
     * other feature vector.
     *
     * @return a feature vector, where each element is the sum of an element
     * in this feature vector and the same element of the other feature vector.
     */    
    public FeatureVector add(FeatureVector other) {
        // Copy this feature vector as the return feature vector
        FeatureVector ret = this.copy();
        // For each element in the other feature vector, if the return vector contains
        // the feature, than add it to the element in the return vector.
        for(Enumeration<Feature> en = other.features.keys(); en.hasMoreElements(); ) {
            Feature key = en.nextElement();
            if(ret.features.containsKey(key)) {
                Double value = ret.features.remove(key);
                value = new Double(value.doubleValue() + other.features.get(key).doubleValue());
                ret.features.put(key, value);
            }
            // Otherwise, if the feature is not in the return vector, than enter
            // it in the vector.
            else {
                ret.features.put(key, other.features.get(key));
            }
        }
        return ret;
    }
    
    /**
     * Returns the element-wise difference between this feature vector and some
     * other feature vector.
     *
     * @return a feature vector, where each element is the difference of an element
     * in this feature vector and the same element of the other feature vector.
     */
    public FeatureVector sub(FeatureVector other) {
        // Copy this feature vector as the return feature vector
        FeatureVector ret = this.copy();
        // For each element in the other feature vector:
        for(Enumeration<Feature> en = other.features.keys(); en.hasMoreElements(); ) {
            Feature key = en.nextElement();
            // if the return vector contains the feature, than subtract the value in
            // the other feature vector from the value in the return feature vector.
            if(ret.features.containsKey(key)) {
                Double value = ret.features.remove(key);
                value = new Double(value.doubleValue() - other.features.get(key).doubleValue());
                ret.features.put(key, value);
            }
            // Otherwise, if the return vector does not contain the feature, then 
            // that means that the feature is 0, so we insert 0-value into the
            // return vector.
            else {
                ret.features.put(key, new Double(0 - other.features.get(key).doubleValue()));
            }
        }
        return ret;        
    }

    /**
     * Returns a new feature vector which contains every element of this vector
     * divided by <code>den</code>.
     */
    public FeatureVector div(double den) {
        FeatureVector ret = new FeatureVector();
        // For each element in the return feature  vector:
        for(Enumeration<Feature> en = this.features.keys(); en.hasMoreElements(); ) {
            Feature key = en.nextElement();
            // Do the division
            double value = this.features.get(key).doubleValue();
            ret.features.put(key, new Double(value/den));
        }
        return ret;
    }
    
    /**
     * Returns an identical copy of the feature vector.
     */
    public FeatureVector copy() {
        FeatureVector ret = new FeatureVector();
        ret.features = new Hashtable<Feature, Double>(this.features);
        return ret;
    }
    
    /**
     * Returns the string representation of this feature vector.
     */
    public String toString() {
        String ret = "[";
        for(Enumeration<Feature> en = features.keys(); en.hasMoreElements();) {
            Feature next = en.nextElement();
            ret += "(" + next + ": " + features.get(next) + ") ";
        }
        ret += "]\n";
        return ret;
    }
    
    /**
     * Returns a summation over all the values in the feature vector.
     */
    public double sum() {
        double ret = 0.0;
        for(Enumeration<Feature> r = features.keys(); r.hasMoreElements();) {
            ret += features.get(r.nextElement()).doubleValue();
        }
        return ret;
    }
    
}
